﻿using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;

namespace Iorz.DomainSearch.Utils.Tools
{
    /// <summary>
    /// Http连接操作帮助类
    /// </summary>
    public class HttpHelper
    {
        /// <summary>
        /// 获取HTTP数据
        /// </summary>
        /// <param name="request">请求参数</param>
        /// <returns></returns>
        public static HttpResponse GetHtml(HttpRequest request)
        {
            HttpWebRequest webRequest = null;
            HttpWebResponse webResponse = null;

            try
            {
                webRequest = request.ConvertToWebRequest();

                using (webResponse = (HttpWebResponse)webRequest.GetResponse())
                {
                    return new HttpResponse(request, webResponse);
                }
            }
            catch (WebException ex)
            {
                if (ex.Response == null)
                    return new HttpResponse(ex);

                try
                {
                    using (webResponse = (HttpWebResponse)ex.Response)
                    {
                        return new HttpResponse(request, webResponse);
                    }
                }
                catch (Exception exp)
                {
                    return new HttpResponse(exp);
                }
            }
            catch (Exception ex)
            {
                return new HttpResponse(ex);
            }
            finally
            {
                if (webRequest != null)
                    webRequest.Abort();

                if (webResponse != null)
                    webResponse.Close();

                webRequest = null;
                webResponse = null;
            }
        }

        /// <summary>
        /// 请求的方法
        /// </summary>
        public enum HttpMethod
        {
            GET,

            POST,

            DELETE,

            PUT
        }

        /// <summary>
        /// Post的数据格式默认为string
        /// </summary>
        public enum PostDataType
        {
            /// <summary>
            /// 字符串类型，这时编码Encoding可不设置
            /// </summary>
            String,

            /// <summary>
            /// Byte类型，需要设置PostdataByte参数的值编码Encoding可设置为空
            /// </summary>
            Byte,

            /// <summary>
            /// 传文件(multipart/form-data)，Postdata必须设置为文件的绝对路径，必须设置Encoding的值
            /// </summary>
            MultipartFile
        }

        /// <summary>
        /// Http请求
        /// </summary>
        public class HttpRequest
        {
            /// <summary>
            /// 获取或设置请求URL地址
            /// </summary>
            public string Url { get; set; }

            /// <summary>
            /// 获取或设置请求的方法
            /// </summary>
            public HttpMethod Method { get; set; }

            /// <summary>
            /// 获取或设置Content-typeHTTP 标头的值
            /// </summary>
            public string ContentType { get; set; }

            /// <summary>
            /// 获取或设置请求标头值 默认为text/html, application/xhtml+xml, */*
            /// </summary>
            public string Accept { get; set; }

            /// <summary>
            /// 请求来源
            /// </summary>
            public string Referer { get; set; }

            /// <summary>
            /// 请求头用户代理信息
            /// </summary>
            public string UserAgent { get; set; }

            /// <summary>
            /// 获取或设置请求标头
            /// </summary>
            public WebHeaderCollection Headers { get; set; }

            /// <summary>
            /// 设置或获取Post参数编码,默认的为utf-8编码
            /// </summary>
            public Encoding PostEncoding { get; set; }

            /// <summary>
            /// 获取或设置返回数据编码,默认为UTF-8,一般为utf-8,gbk,gb2312,为null则可以自动识别
            /// </summary>
            public Encoding Encoding { get; set; }

            /// <summary>
            /// 获取或设置默认请求超时时间
            /// </summary>
            public int Timeout { get; set; }

            /// <summary>
            /// 该值指示请求是否应跟随重定向响应
            /// </summary>
            public bool AllowAutoRedirect { get; set; }

            /// <summary>
            /// 获取或设置一个值，该值指示是否与 Internet 资源建立持久性连接默认为true。
            /// </summary>
            public bool KeepAlive { get; set; }

            /// <summary>
            /// 是否使用 100-Continue 行为，默认为true
            /// </summary>
            public bool Expect100Continue { get; set; }

            /// <summary>
            /// 获取或设置Cookie对象集合(keyvalue键值对)
            /// </summary>
            public string Cookies { get; set; }

            /// <summary>
            /// 获取或设置请求的身份验证信息
            /// </summary>
            public ICredentials ICredentials { get; set; }

            /// <summary>
            /// Post的数据类型，默认字符串
            /// </summary>
            public PostDataType PostDataType { get; set; }

            /// <summary>
            /// 获取或设置Post/PUT请求时要发送的数据，PostDataType为FilePath时为文件路径
            /// </summary>
            public string Postdata { get; set; }

            /// <summary>
            /// 获取或设置Post请求时要发送的byte数据
            /// </summary>
            public byte[] PostdataByte { get; set; }

            /// <summary>
            /// PostDataType为MultipartFile时，需设置上传文件的ContentType
            /// </summary>
            public string FileContentType { get; set; }

            public HttpRequest()
            {
                Method = HttpMethod.GET;
                ContentType = "application/json";
                Accept = "text/html, application/xhtml+xml, */*";
                UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36";
                Headers = new WebHeaderCollection();
                PostEncoding = Encoding.UTF8;
                Encoding = Encoding.UTF8;
                Timeout = 15000;
                AllowAutoRedirect = false;
                KeepAlive = false;
                Expect100Continue = true;
                Cookies = string.Empty;
                PostDataType = PostDataType.String;
            }

            /// <summary>
            /// 转换为Http web请求
            /// </summary>
            /// <returns></returns>
            public HttpWebRequest ConvertToWebRequest()
            {
                HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(Url);
                webRequest.Proxy = null;
                webRequest.Method = Method.ToString();
                webRequest.ContentType = ContentType;
                webRequest.Timeout = Timeout;
                webRequest.KeepAlive = KeepAlive;
                webRequest.Accept = Accept;
                webRequest.AllowAutoRedirect = AllowAutoRedirect;
                webRequest.Referer = Referer;
                webRequest.UserAgent = UserAgent;
                webRequest.ServicePoint.Expect100Continue = Expect100Continue;

                if (webRequest.ServicePoint.ConnectionName == "https")
                    webRequest.ProtocolVersion = webRequest.ServicePoint.ProtocolVersion;

                // 设置安全凭证
                if (ICredentials != null)
                    webRequest.Credentials = ICredentials;

                if (Headers != null && Headers.Count > 0)
                {
                    foreach (string key in Headers.AllKeys)
                    {
                        webRequest.Headers[key] = Headers[key];
                    }
                }

                if (!string.IsNullOrEmpty(Cookies))
                    webRequest.Headers["Cookie"] = Cookies;

                SetPostData(webRequest);

                return webRequest;
            }

            /// <summary>
            /// 设置Post数据
            /// </summary>
            /// <param name="request"></param>
            private void SetPostData(HttpWebRequest request)
            {
                if (Method != HttpMethod.POST && Method != HttpMethod.PUT)
                    return;

                byte[] buffer = null;
                if (PostEncoding == null) PostEncoding = Encoding.UTF8;

                if (PostDataType == PostDataType.Byte && PostdataByte != null && PostdataByte.Length > 0) // 写入Byte类型
                {
                    buffer = PostdataByte;
                }
                else if (PostDataType == PostDataType.MultipartFile && !string.IsNullOrEmpty(Postdata)) // 写入文件
                {
                    PostFileByMultipart(request);
                }
                else if (!string.IsNullOrEmpty(Postdata)) // 写入字符串
                {
                    buffer = PostEncoding.GetBytes(Postdata);
                }

                if (buffer != null)
                {
                    request.ContentLength = buffer.Length;
                    request.GetRequestStream().Write(buffer, 0, buffer.Length);
                }
            }

            /// <summary>
            /// multipart/form-data方式上传文件
            /// </summary>
            /// <param name="request"></param>
            private void PostFileByMultipart(HttpWebRequest request)
            {
                string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
                byte[] boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

                request.ContentType = "multipart/form-data; boundary=" + boundary;
                request.Timeout = 99999;

                Stream rs = request.GetRequestStream();
                rs.Write(boundarybytes, 0, boundarybytes.Length);

                string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
                string header = string.Format(headerTemplate, "file", Path.GetFileName(Postdata), FileContentType);
                byte[] headerbytes = PostEncoding.GetBytes(header);
                rs.Write(headerbytes, 0, headerbytes.Length);

                using (FileStream fileStream = new FileStream(Postdata, FileMode.Open, FileAccess.Read))
                {
                    byte[] buffer = new byte[4096];
                    int bytesRead = 0;
                    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        rs.Write(buffer, 0, bytesRead);
                    }
                    fileStream.Close();
                }

                byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
                rs.Write(trailer, 0, trailer.Length);
                rs.Close();
            }
        }

        /// <summary>
        /// Http响应
        /// </summary>
        public class HttpResponse
        {
            /// <summary>
            /// 响应Content-type HTTP 标头的值
            /// </summary>
            public string ContentType { get; set; }

            /// <summary>
            /// 响应标头
            /// </summary>
            public WebHeaderCollection Headers { get; set; }

            /// <summary>
            /// 返回状态码
            /// </summary>
            public HttpStatusCode StatusCode { get; set; }

            /// <summary>
            /// 返回状态说明
            /// </summary>
            public string StatusDescription { get; set; }

            /// <summary>
            /// 返回值为image时直接使用byte数组
            /// </summary>
            public byte[] ResponseBytes { get; set; }

            /// <summary>
            /// 响应的文本信息
            /// </summary>
            public string Html { get; set; }

            /// <summary>
            /// Cookie对象集合
            /// </summary>
            public CookieCollection Cookies { get; set; }

            /// <summary>
            /// Http请求异常
            /// </summary>
            public Exception Exception { get; set; }

            public HttpResponse(HttpRequest request, HttpWebResponse response)
            {
                ContentType = response.ContentType;
                Headers = response.Headers;
                StatusCode = response.StatusCode;
                StatusDescription = response.StatusDescription;
                Cookies = response.Cookies;

                byte[] responseBytes = GetResponseBytes(response);
                if (responseBytes != null && responseBytes.Length > 0)
                {
                    if (ContentType.ToLower().Contains("image"))
                    {
                        ResponseBytes = responseBytes;
                    }
                    else
                    {
                        CheckEncoding(request, response, responseBytes);
                        if (request.Encoding == Encoding.UTF8)
                            Html = GetUTF8String(responseBytes);
                        else
                            Html = request.Encoding.GetString(responseBytes);
                    }
                }
                else
                {
                    Html = string.Empty;
                }
            }

            public HttpResponse(Exception ex)
            {
                Exception = ex;
            }

            /// <summary>
            /// 提取网页Byte
            /// </summary>
            private static byte[] GetResponseBytes(HttpWebResponse response)
            {
                byte[] responseBytes = null;
                MemoryStream _stream = new MemoryStream();

                //GZIIP处理
                if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))
                {
                    //开始读取流并设置编码方式
                    _stream = GetMemoryStream(new GZipStream(response.GetResponseStream(), CompressionMode.Decompress));
                }
                else
                {
                    //开始读取流并设置编码方式
                    _stream = GetMemoryStream(response.GetResponseStream());
                }

                //获取Byte
                responseBytes = _stream.ToArray();

                _stream.Close();
                _stream = null;

                return responseBytes;
            }

            /// <summary>
            /// 转为内存流
            /// </summary>
            private static MemoryStream GetMemoryStream(Stream streamResponse)
            {
                MemoryStream _stream = new MemoryStream();

                int Length = 256;
                Byte[] buffer = new Byte[Length];
                int bytesRead = streamResponse.Read(buffer, 0, Length);
                while (bytesRead > 0)
                {
                    _stream.Write(buffer, 0, bytesRead);
                    bytesRead = streamResponse.Read(buffer, 0, Length);
                }

                return _stream;
            }

            /// <summary>
            /// 检测内容编码
            /// </summary>
            private static void CheckEncoding(HttpRequest request, HttpWebResponse response, byte[] responseBytes)
            {
                if (request.Encoding != null)
                    return;

                Encoding encoding;
                Match meta = Regex.Match(Encoding.Default.GetString(responseBytes), "<meta[^<]*charset=([^<]*)[\"']", RegexOptions.IgnoreCase);

                string c = string.Empty;
                if (meta != null && meta.Groups.Count > 0)
                {
                    c = meta.Groups[1].Value.ToLower().Trim();
                }

                if (c.Length > 2)
                {
                    try
                    {
                        encoding = Encoding.GetEncoding(c.Replace("\"", string.Empty).Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim());
                    }
                    catch
                    {
                        if (string.IsNullOrEmpty(response.CharacterSet))
                        {
                            encoding = Encoding.UTF8;
                        }
                        else
                        {
                            encoding = Encoding.GetEncoding(response.CharacterSet);
                        }
                    }
                }
                else
                {
                    if (string.IsNullOrEmpty(response.CharacterSet))
                    {
                        encoding = Encoding.UTF8;
                    }
                    else
                    {
                        encoding = Encoding.GetEncoding(response.CharacterSet);
                    }
                }

                request.Encoding = encoding;
            }

            /// <summary>
            /// UTF8编码格式去除BOM头
            /// </summary>
            private static string GetUTF8String(byte[] buffer)
            {
                if (buffer == null)
                    return null;

                if (buffer.Length <= 3)
                {
                    return Encoding.UTF8.GetString(buffer);
                }

                byte[] bomBuffer = new byte[] { 0xef, 0xbb, 0xbf };

                if (buffer[0] == bomBuffer[0]
                    && buffer[1] == bomBuffer[1]
                    && buffer[2] == bomBuffer[2])
                {
                    return new UTF8Encoding(false).GetString(buffer, 3, buffer.Length - 3);
                }

                return Encoding.UTF8.GetString(buffer);
            }
        }
    }
}
